package vip.zhenzicheng.nio.nio.writeable;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.Set;

import static vip.zhenzicheng.nio.Const.response;

/**
 * @author zhenzicheng
 * @date 2022-06-06 15:54
 */
public class NioServerHandleWritable implements Runnable {

  private Selector selector;
  private ServerSocketChannel serverSocketChannel;
  private volatile boolean started;

  public NioServerHandleWritable(int port) {
    try {
      // 创建选择器
      selector = Selector.open();
      // 打开监听通道
      serverSocketChannel = ServerSocketChannel.open();
      serverSocketChannel.configureBlocking(false);
      // 绑定端口
      serverSocketChannel.socket()
          .bind(new InetSocketAddress(port), 1024);
      // 监听客户端连接请求
      serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
      // 标记服务已开启
      started = true;
      System.out.println("服务器已启动，端口号：" + port);

    } catch (IOException e) {
      e.printStackTrace();
      System.exit(1);
    }

  }

  @Override
  public void run() {
    while (started) {
      try {
        // 阻塞，当至少一个注册的事件发生时才继续
        selector.select();
        Set<SelectionKey> keys = selector.selectedKeys();
        Iterator<SelectionKey> iterator = keys.iterator();
        SelectionKey key;
        System.out.println("------由线程[" + Thread.currentThread().getName() + "]处理------");
        while (iterator.hasNext()) {
          key = iterator.next();
          iterator.remove();
          try {
            handleInput(key);
          } catch (Exception e) {
            if (key != null) {
              key.cancel();
              if (key.channel() != null) {
                key.channel().close();
              }
            }
          }
        }
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
    if (selector != null) {
      try {
        selector.close();
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  }

  private void handleInput(SelectionKey key) throws IOException {
    System.out.println("当前通道的事件：" + key.interestOps());
    if (key.isValid()) {
      // 处理新接入的请求消息
      if (key.isAcceptable()) {
        // 获取关心当前事件的channel
        ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
        // 通过accept创建SocketChannel实例，完成意味着TCP三次握手，TCP物理链路正式建立
        SocketChannel sc = ssc.accept();
        System.out.println("socket channel 建立连接");
        sc.configureBlocking(false);
        // 注册读事件
        sc.register(selector, SelectionKey.OP_READ);
      }

      // 读消息
      if (key.isReadable()) {
        System.out.println("socket channel 数据准备完成，可以读取");
        SocketChannel sc = (SocketChannel) key.channel();
        // 创建ByteBuffer,开辟1M缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        int read = sc.read(buffer);
        if (read > 0) {
          buffer.flip(); // 用于后续对buffer读取
          byte[] bytes = new byte[buffer.remaining()];
          // 将缓冲区可读字节数组复制到新数组中
          buffer.get(bytes);
          String msg = new String(bytes, StandardCharsets.UTF_8);
          System.out.println("服务器收到消息：" + msg);
          // 处理数据
          String result = response(msg);
          // 发送应答消息
          doWrite(sc, result);
        } else if (read < 0) { // 链路已关闭，释放资源
          key.channel();
          sc.close();
        }
      }

      // 写事件
      if (key.isWritable()) {
        System.out.println("socket channel 缓冲为空，准备写");
        SocketChannel sc = (SocketChannel) key.channel();
        ByteBuffer buffer = (ByteBuffer) key.attachment(); // 拿到doWrite()方法register时附加的的writeBuffer，实现了共享变量
        if (buffer != null && buffer.hasRemaining()) {
          int count = sc.write(buffer);
          System.out.println("write:" + count + " byte,remaining:" + buffer.hasRemaining());
        } else {
          key.attach(null); // 将指向共享的writeBuffer的引用置空，等待垃圾回收
          // 取消对写的注册，只关注读
          System.out.println("没有数据需要写，取消写注册");
          key.interestOps(SelectionKey.OP_READ);
        }
      }
    }
  }

  private void doWrite(SocketChannel channel, String response) throws ClosedChannelException {
    byte[] bytes = response.getBytes();
    ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
    // 将字节数组复制到缓冲区
    writeBuffer.put(bytes);
    writeBuffer.flip();
    channel.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ, writeBuffer);
  }

  public void stop() {
    started = false;
  }

}
